﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO.Ports;
using System.Management;
using System.Threading;

using Leap;

namespace RemoteHand
{

    #region frmMain

    public partial class frmMain : Form
    {

        // シリアルポートコンボのキーと値をセットで保存。「COM1」が「標準シリアルポート1」等。
        Dictionary<string, string> ComPortTable = new Dictionary<string, string>();

        // Leap Motionのリスナとコントローラ
        LeapListener listener;
        Controller controller;

        // 指角度格納用。
        private static float[] fingerAngle = new float[5];

        // 送信データ格納用。  
        private static byte[] sendData = new byte[8];

        public frmMain()
        {
            InitializeComponent();
        }

        #region ユーザ関数

        /// <summary>
        /// シリアルポートの実装状況を取得する。
        /// </summary>
        /// <remarks>
        /// 以下の変数を宣言すること。
        /// Dictionary<string, string> ComPortTable = new Dictionary<string, string>();
        /// </remarks>
        void ConfigureComPortTable()
        {
            // シリアルポートの実装状況を取得。
            ManagementClass mcW32SerialPort = new ManagementClass("Win32_SerialPort");
            foreach (ManagementObject aSerialPort in mcW32SerialPort.GetInstances())
            {
                ComPortTable.Add(
                    (string)aSerialPort.GetPropertyValue("Caption"),
                    (string)aSerialPort.GetPropertyValue("DeviceID"));
            }
            string[] ports = SerialPort.GetPortNames();
            foreach (string port in ports)
            {
                if (!ComPortTable.ContainsValue(port))
                {
                    ComPortTable.Add(port, port);
                }
            }
        }

        /// <summary>
        /// Leap Motion Listener の OnFrame イベントから間接的に呼び出される
        /// UIにもアクセス可能なハンドラ。
        /// フレーム（データ）更新に対する主な処理をここで行う。
        /// </summary>
        /// <param name="contorller"></param>
        private void listener_OnFrameEvent(Controller contorller)
        {
            Vector handDirection;   // 手のひらの向き。

            // 最新のフレームを取得。
            Frame frame = controller.Frame();

            // 手を検出していれば、
            if (!frame.Hands.IsEmpty)
            {
                // 1つ目の手を取得。
                Hand hand = frame.Hands[0];

                // 手の向きを取得。
                handDirection = hand.Direction;

                // 指を検出できていれば、
                FingerList fingers = hand.Fingers;
                if (!fingers.IsEmpty)
                {
                    // 親指の検出。
                    FingerList thumbs = hand.Fingers.FingerType(Finger.FingerType.TYPE_THUMB);
                    if (!thumbs.IsEmpty)
                    {
                        Finger thumb = thumbs.Rightmost;
                        Vector thumbDirection = thumb.Direction;
                        fingerAngle[0] =
                            thumbDirection.AngleTo(handDirection) * 180.0f / (float)Math.PI;
                        labelThumb.Text = fingerAngle[0].ToString();
                        trackBarThumb.Value = 180 - (int)fingerAngle[0];
                        sendData[2] = (byte)(fingerAngle[0] * 1.5);
                    }

                    // 人差し指の検出。
                    FingerList indexes = hand.Fingers.FingerType(Finger.FingerType.TYPE_INDEX);
                    if (!indexes.IsEmpty)
                    {
                        Finger index = indexes.Rightmost;
                        Vector indexDirection = index.Direction;
                        fingerAngle[1] =
                            indexDirection.AngleTo(handDirection) * 180.0f / (float)Math.PI;
                        labelIndex.Text = fingerAngle[1].ToString();
                        trackBarIndex.Value = 180 - (int)fingerAngle[1];
                        sendData[3] = (byte)(fingerAngle[1] * 1.5);
                    }

                    // 中指の検出。
                    FingerList middles = hand.Fingers.FingerType(Finger.FingerType.TYPE_MIDDLE);
                    if (!middles.IsEmpty)
                    {
                        Finger middle = middles.Rightmost;
                        Vector middleDirection = middle.Direction;
                        fingerAngle[2] =
                            middleDirection.AngleTo(handDirection) * 180.0f / (float)Math.PI;
                        labelMiddle.Text = fingerAngle[2].ToString();
                        trackBarMiddle.Value = 180 - (int)fingerAngle[2];
                        sendData[4] = (byte)(fingerAngle[2] * 1.5);
                    }

                    // 薬指の検出。
                    FingerList rings = hand.Fingers.FingerType(Finger.FingerType.TYPE_RING);
                    if (!rings.IsEmpty)
                    {
                        Finger ring = rings.Rightmost;
                        Vector ringDirection = ring.Direction;
                        fingerAngle[3] =
                            ringDirection.AngleTo(handDirection) * 180.0f / (float)Math.PI;
                        labelRing.Text = fingerAngle[3].ToString();
                        trackBarRing.Value = 180 - (int)fingerAngle[3];
                        sendData[5] = (byte)(fingerAngle[3] * 1.5);
                    }

                    // 小指の検出。
                    FingerList pinkys = hand.Fingers.FingerType(Finger.FingerType.TYPE_PINKY);
                    if (!pinkys.IsEmpty)
                    {
                        Finger pinky = pinkys.Rightmost;
                        Vector pinkyDirection = pinky.Direction;
                        fingerAngle[4] =
                            pinkyDirection.AngleTo(handDirection) * 180.0f / (float)Math.PI;
                        labelPinky.Text = fingerAngle[4].ToString();
                        trackBarPinky.Value = 180 - (int)fingerAngle[4];
                        sendData[6] = (byte)(fingerAngle[4] * 1.5);
                    }

                }

            }
        }

        #endregion ユーザ関数

        /// <summary>
        /// frmMainロード時ハンドラ。初期化処理を行う。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void frmMain_Load(object sender, EventArgs e)
        {
            // 変数初期化。
            for (int i = 0; i < 5; i++)
                fingerAngle[i] = 0.0f;
            sendData[0] = 0x02;
            sendData[1] = 0xff;
            for (int i = 2; i < 7; i++)
                sendData[i] = 0x00;
            sendData[7] = 0x03;

            // シリアルポートの実装状況を取得。
            ConfigureComPortTable();

            // 画面の構成
            //  シリアルポートのコンボボックスの内容を構成。
            comboBoxPortNo.Items.Clear();
            foreach (string aPortCaption in ComPortTable.Keys)
            {
                comboBoxPortNo.Items.Add(aPortCaption);
            }
            if (ComPortTable.Count > 0)
            {
                comboBoxPortNo.SelectedIndex = 0;
            }
            else
            {
                MessageBox.Show("シリアルポートが存在しません。");
            }
            //  各コントロールの有効化、無効化設定。
            buttonConnect.Enabled = true;
            buttonDisconnect.Enabled = false;
            buttonControlOn.Enabled = false;
            buttonControlOff.Enabled = false;
            
        }

        /// <summary>
        /// 「終了」ボタン押下のハンドラ。終了処理を行う。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonEnd_Click(object sender, EventArgs e)
        {

            this.Close();
        }

        /// <summary>
        /// 「接続」ボタン押下のハンドラ。Arduino（シリアルポート）への
        /// 接続を行う。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonConnect_Click(object sender, EventArgs e)
        {
            // 「接続」ボタン、ポートコンボを一旦無効化。
            buttonConnect.Enabled = false;
            comboBoxPortNo.Enabled = false;

            try
            {
                string comPort = ComPortTable[comboBoxPortNo.SelectedItem.ToString()];
                if (comPort != null)
                {
                    // COMポートへ接続。
                    serialPort1.PortName = comPort;
                    if (serialPort1.IsOpen)
                    {
                        // 接続エラーメッセージ表示。
                        MessageBox.Show("既に " + comPort + " はオープンされています。");

                        // コントロールの有効・無効化。
                        buttonConnect.Enabled = true;
                        comboBoxPortNo.Enabled = true;

                    }
                    else
                    {
                        // 通信条件を設定。
                        //  ボーレート
                        serialPort1.BaudRate = 9600;
                        //  パリティ
                        serialPort1.Parity = System.IO.Ports.Parity.None;
                        //  ストップビット
                        serialPort1.StopBits = System.IO.Ports.StopBits.One;
                        //  データ長
                        serialPort1.DataBits = 8;

                        // ポートをオープン。
                        serialPort1.Open();

                        // コントロールの有効・無効化。
                        buttonDisconnect.Enabled = true;
                        buttonControlOn.Enabled = true;
                        buttonControlOff.Enabled = false;

                    }
                }
            }
            catch (Exception ex)
            {
                if (serialPort1.IsOpen) serialPort1.Close();
                string errMsg = "@buttonConnect_Click():\n";
                errMsg += ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
                // コントロールの有効・無効化。
                buttonConnect.Enabled = true;
                comboBoxPortNo.Enabled = true;
                buttonDisconnect.Enabled = false;
                buttonControlOn.Enabled = false;
                buttonControlOff.Enabled = false;
            }
        }

        /// <summary>
        /// 「切断」ボタン押下のハンドラ。シリアルポートの切断を行う。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonDisconnect_Click(object sender, EventArgs e)
        {
            // 「切断」ボタンを一旦無効化。
            buttonDisconnect.Enabled = false;

            try
            {
                // ポートがオープン状態なら、
                if (serialPort1.IsOpen)
                {
                    // ポートをクローズ。
                    serialPort1.Close();

                    // コントロールの表示を変更。
                    buttonConnect.Enabled = true;
                    comboBoxPortNo.Enabled = true;
                    buttonControlOn.Enabled = false;
                    buttonControlOff.Enabled = false;
                    
                }
            }
            catch (Exception ex)
            {
                string errMsg = ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
            }
        }

        /// <summary>
        /// 「On」（制御オン）ボタン押下のハンドラ。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonControlOn_Click(object sender, EventArgs e)
        {
            // 一旦、「On」ボタン等を無効化。
            buttonControlOn.Enabled = false;
            buttonDisconnect.Enabled = false;

            try
            {
                // Leap Motion Listener と Controller のインスタンス作成。
                listener = new LeapListener();
                controller = new Controller();

                // イベントハンドラ登録。
                listener.OnFrameEvent += listener_OnFrameEvent;

                // サンプルのリスナがコントローラからイベントを受け取るように設定。
                controller.AddListener(listener);

                // コントロールの有効・無効化。
                buttonControlOff.Enabled = true;
            }
            catch (Exception ex)
            {
                string errMsg = ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
                // コントロールの有効・無効化。
                buttonControlOn.Enabled = true;
                buttonControlOff.Enabled = false;
                buttonDisconnect.Enabled = true;
            }
        }

        /// <summary>
        /// 「Off」（制御オフ）ボタン押下のハンドラ。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonControlOff_Click(object sender, EventArgs e)
        {
            // 一旦、「Off」ボタンを無効化。
            buttonControlOff.Enabled = false;

            try
            {
                // 終了時にリスナを削除。
                controller.RemoveListener(listener);
                controller.Dispose();

                // コントロールの有効・無効化。
                buttonControlOn.Enabled = true;
                buttonDisconnect.Enabled = true;
            }
            catch (Exception ex)
            {
                string errMsg = ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
                // コントロールの有効・無効化。
                buttonControlOn.Enabled = false;
                buttonControlOff.Enabled = true;
                buttonDisconnect.Enabled = false;
            }
            
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                serialPort1.Write(sendData, 0, 8);
            }
        }

    }

    #endregion frmMain

    #region Leap Motion Listener

    class LeapListener : Listener
    {
        // UIスレッドに戻すためのコンテキスト。
        SynchronizationContext context = SynchronizationContext.Current;

        // イベントハンドラデリゲート。
        public delegate void LeapListenerEvent(Controller controller);

        // イベントハンドラ。
        public event LeapListenerEvent OnInitEvent;
        public event LeapListenerEvent OnConnectEvent;
        public event LeapListenerEvent OnDisConnectEvent;
        public event LeapListenerEvent OnExitEvent;
        public event LeapListenerEvent OnFocusGainedEvent;
        public event LeapListenerEvent OnFocusLostEvent;
        public event LeapListenerEvent OnFrameEvent;

        // イベントの発行。
        private void Invoke(Controller controller, LeapListenerEvent handler)
        {
            // UIスレッドに同期的に処理を戻す。
            context.Post(state => { if (handler != null) { handler(controller); } }, null);

        }

        // デバッグ用コンソール出力。
        private Object thisLock = new Object();
        
        private void SafeWriteLine(String line)
        {
            lock (thisLock)
            {
                Console.WriteLine(line);    // コンソール出力。
            }
        }     
        

        /// <summary>
        /// リスナの登録先のコントローラが初期化されたときににコールされる。
        /// 初期化処理をここで行う。
        /// </summary>
        /// <param name="controller"></param>
        public override void OnInit(Controller controller)
        {
            SafeWriteLine("Initialized");

            Invoke(controller, OnInitEvent);
        }

        /// <summary>
        /// コントローラがLeap Motionに接続して、モーショントラッキングデータの
        /// フレーム送信開始準備が整った時にコールされる。
        /// </summary>
        /// <param name="controller"></param>
        /// <remarks>
        /// 今回は、このタイミングで有効にするジェスチャーを定義している。
        /// </remarks>
        public override void OnConnect(Controller controller)
        {
            SafeWriteLine("Connected");

            Invoke(controller, OnConnectEvent);
/*
            controller.EnableGesture(Gesture.GestureType.TYPE_CIRCLE);
            controller.EnableGesture(Gesture.GestureType.TYPE_KEY_TAP);
            controller.EnableGesture(Gesture.GestureType.TYPE_SCREEN_TAP);
            controller.EnableGesture(Gesture.GestureType.TYPE_SWIPE);
 * */
        }

        /// <summary>
        /// コントローラがLeap Motionから接続解除（デバイス取り外し等）した
        /// ときにコールされる。
        /// </summary>
        /// <param name="controller"></param>
        public override void OnDisconnect(Controller controller)
        {
            SafeWriteLine("Disconnected");

            Invoke(controller, OnDisConnectEvent);
        }

        /// <summary>
        /// リスタがコントローラから削除されたとき（あるいは、コントローラが
        /// 破棄されたとき）にコールされる。終了処理をここで行う。
        /// </summary>
        /// <param name="controller"></param>
        public override void OnExit(Controller controller)
        {
            SafeWriteLine("Exited");

            Invoke(controller, OnExitEvent);
        }

        /// <summary>
        /// アプリケーションがアクティブ（最前面）になったときにコールされる。
        /// </summary>
        /// <param name="controller"></param>
        public override void OnFocusGained(Controller controller)
        {
            SafeWriteLine("Focused");

            Invoke(controller, OnFocusGainedEvent);
        }

        /// <summary>
        /// アプリケーションが非アクティブ（フォーカスが外れた）になったときにコールされる。
        /// </summary>
        /// <param name="controller"></param>
        public override void OnFocusLost(Controller controller)
        {
            SafeWriteLine("Unfocused");

            Invoke(controller, OnFocusLostEvent);
        }

        /// <summary>
        /// モーショントラッキングデータの新しいフレームが利用可能になった
        /// ときにコールされる。データ変更に対するメインの処理をここで行う。
        /// </summary>
        /// <param name="controller"></param>
        public override void OnFrame(Controller controller)
        {
            Invoke(controller, OnFrameEvent);
        }
    }

    #endregion Leap Motion Listener
}
